/*
 * Copyright (c) 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.auzingers.docsorganizer.gui.chooseaccount;

import com.auzingers.docsorganizer.exception.UserCanceledOperation;

import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.IAccountManagerResponse;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;

import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public abstract class AmsTask extends FutureTask<Bundle> implements AccountManagerFuture<Bundle> {
    final protected IAccountManagerResponse mResponse;
    final protected Handler mHandler;
    final protected AccountManagerCallback<Bundle> mCallback;
    final protected Activity mActivity;

    public AmsTask(Activity activity, Handler handler, AccountManagerCallback<Bundle> callback) {
        super(new Callable<Bundle>() {
            public Bundle call() throws Exception {
                throw new IllegalStateException("this should never be called");
            }
        });

        mHandler = handler;
        mCallback = callback;
        mActivity = activity;
        mResponse = new Response();
    }

    public final AccountManagerFuture<Bundle> start() {
        try {
            doWork();
        } catch (RemoteException e) {
            setException(e);
        }
        return this;
    }

    public abstract void doWork() throws RemoteException;

    private Bundle internalGetResult(Long timeout, TimeUnit unit) throws OperationCanceledException, IOException,
            AuthenticatorException {
        try {
            if (timeout == null) {
                return get();
            } else {
                return get(timeout, unit);
            }
        } catch (CancellationException e) {
            throw new OperationCanceledException();
        } catch (TimeoutException e) {
            // fall through and cancel
        } catch (InterruptedException e) {
            // fall through and cancel
        } catch (ExecutionException e) {
            final Throwable cause = e.getCause();
            if (cause instanceof IOException) {
                throw (IOException) cause;
            } else if (cause instanceof UnsupportedOperationException) {
                throw new AuthenticatorException(cause);
            } else if (cause instanceof UserCanceledOperation) {
                throw new UserCanceledOperation(cause);
            } else if (cause instanceof AuthenticatorException) {
                throw (AuthenticatorException) cause;
            } else if (cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            } else if (cause instanceof Error) {
                throw (Error) cause;
            } else {
                throw new IllegalStateException(cause);
            }
        } finally {
            cancel(true /* interrupt if running */);
        }
        throw new OperationCanceledException();
    }

    public Bundle getResult() throws OperationCanceledException, IOException, AuthenticatorException {
        return internalGetResult(null, null);
    }

    public Bundle getResult(long timeout, TimeUnit unit) throws OperationCanceledException, IOException,
            AuthenticatorException {
        return internalGetResult(timeout, unit);
    }

    protected void done() {
        if (mCallback != null) {
            postToHandler(mHandler, mCallback, this);
        }
    }

    private void postToHandler(Handler handler, final AccountManagerCallback<Bundle> callback,
            final AccountManagerFuture<Bundle> future) {
        handler.post(new Runnable() {
            public void run() {
                callback.run(future);
            }
        });
    }

    /** Handles the responses from the AccountManager */
    private class Response extends IAccountManagerResponse.Stub {
        public void onResult(Bundle bundle) {
            Intent intent = bundle.getParcelable("intent");
            if (intent != null && mActivity != null) {
                // since the user provided an Activity we will silently start intents
                // that we see
                mActivity.startActivity(intent);
                // leave the Future running to wait for the real response to this request
            } else if (bundle.getBoolean("retry")) {
                try {
                    doWork();
                } catch (RemoteException e) {
                    // this will only happen if the system process is dead, which means
                    // we will be dying ourselves
                }
            } else {
                set(bundle);
            }
        }

        public void onError(int code, String message) {
            if (code == AccountManager.ERROR_CODE_CANCELED) {
                setException(new UserCanceledOperation());
                
                // the authenticator indicated that this request was canceled, do so now
                //cancel(true /* mayInterruptIfRunning */);
                return;
            }
            setException(convertErrorToException(code, message));
        }
    }
    
    public static Exception convertErrorToException(int code, String message) {
        if (code == AccountManager.ERROR_CODE_NETWORK_ERROR) {
            return new IOException(message);
        }

        if (code == AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION) {
            return new UnsupportedOperationException(message);
        }

        if (code == AccountManager.ERROR_CODE_INVALID_RESPONSE) {
            return new AuthenticatorException(message);
        }

        if (code == AccountManager.ERROR_CODE_BAD_ARGUMENTS) {
            return new IllegalArgumentException(message);
        }

        return new AuthenticatorException(message);
    }
}
